package cn.harry.wx.service.impl;

import cn.harry.config.TaskExcutor;
import cn.harry.wx.dao.WxUserDao;
import cn.harry.wx.entity.WxUser;
import cn.harry.wx.service.WxUserService;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.WxMpUserService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.result.WxMpUserList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 用户表
 *
 * @author honghh
 * Date 2021-04-08 16:19:27
 * Copyright (C) www.tech-harry.cn
 */
@Slf4j
@Service("wxUserService")
public class WxUserServiceImpl extends ServiceImpl<WxUserDao, WxUser> implements WxUserService {
    @Autowired
    private WxMpService wxMpService;

    private volatile static boolean syncWxUserTaskRunning = false;

    @Override
    public IPage<WxUser> getPage(WxUser wxUser, Integer pageSize, Integer pageNum) {
        LambdaQueryWrapper<WxUser> wrapper = new LambdaQueryWrapper<>(wxUser);
        if (StrUtil.isNotEmpty(wxUser.getBeginTime())) {
            wrapper.gt(WxUser::getCreateTime, wxUser.getBeginTime());
        }
        if (StrUtil.isNotEmpty(wxUser.getEndTime())) {
            wrapper.lt(WxUser::getCreateTime, wxUser.getEndTime());
        }
        return page(new Page<>(pageNum, pageSize), wrapper);
    }


    @Override
    public WxUser selectById(Long id) {
        return this.baseMapper.selectById(id);
    }

    @Override
    public int create(WxUser wxUser) {
        return this.baseMapper.insert(wxUser);
    }


    @Override
    public int update(Long id, WxUser wxUser) {
        wxUser.setId(id);
        return this.baseMapper.updateById(wxUser);
    }


    @Override
    public int deleteByIds(String[] ids) {
        return this.baseMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 根据openid更新用户信息
     *
     * @param openid
     * @return
     */
    @Override
    public WxUser refreshUserInfo(String openid, String appid) {
        try {
            // 获取微信用户基本信息
            log.info("更新用户信息，openid={}", openid);
            wxMpService.switchover(appid);
            WxMpUser userWxInfo = wxMpService.getUserService().userInfo(openid, null);
            if (userWxInfo == null) {
                log.error("获取不到用户信息，无法更新,openid:{}", openid);
                return null;
            }
            log.info("WxMpUser:===>{}", userWxInfo.toString());
            WxUser user = new WxUser(userWxInfo, appid);
            this.saveOrUpdate(user);
            return user;
        } catch (Exception e) {
            log.error("更新用户信息失败,openid:{}", openid);
        }
        return null;
    }

    @Override
    public void unsubscribe(String openid) {
        this.baseMapper.unsubscribe(openid);
    }

    @Override
    public IPage<WxUser> queryPage(Map<String, Object> params) {
        String openid = (String) params.get("openid");
        String nickname = (String) params.get("nickname");
        String appid = (String) params.get("appid");
        String city = (String) params.get("city");
        String qrSceneStr = (String) params.get("qrSceneStr");
        long pageNum = Long.parseLong((String) params.get("page"));
        long pageSize = Long.parseLong((String) params.get("limit"));
        LambdaQueryWrapper<WxUser> wrapper = new LambdaQueryWrapper<>();
        if (StrUtil.isNotEmpty(appid)) {
            wrapper.eq(WxUser::getAppid, appid);
        }
        if (StrUtil.isNotEmpty(openid)) {
            wrapper.eq(WxUser::getOpenid, openid);
        }
        if (StrUtil.isNotEmpty(nickname)) {
            wrapper.like(WxUser::getNickname, nickname);
        }
        if (StrUtil.isNotEmpty(city)) {
            wrapper.eq(WxUser::getCity, city);
        }
        if (StrUtil.isNotEmpty(qrSceneStr)) {
            wrapper.eq(WxUser::getQrSceneStr, qrSceneStr);
        }
        return page(new Page<>(pageNum, pageSize), wrapper);
    }


    /**
     * 同步用户列表,公众号一次拉取调用最多拉取10000个关注者的OpenID，可以通过传入nextOpenid参数多次拉取
     */
    @Override
    @Async
    public void syncWxUsers(String appid) {
        //同步较慢，防止个多线程重复执行同步任务
        Assert.isTrue(!syncWxUserTaskRunning, "后台有同步任务正在进行中，请稍后重试");
        wxMpService.switchoverTo(appid);
        syncWxUserTaskRunning = true;
        log.info("同步公众号粉丝列表：任务开始");
        wxMpService.switchover(appid);
        boolean hasMore = true;
        String nextOpenid = null;
        WxMpUserService wxMpUserService = wxMpService.getUserService();
        try {
            int page = 1;
            while (hasMore) {
                //拉取openid列表，每次最多1万个
                WxMpUserList wxMpUserList = wxMpUserService.userList(nextOpenid);
                log.info("拉取openid列表：第{}页，数量：{}", page++, wxMpUserList.getCount());
                List<String> openids = wxMpUserList.getOpenids();
                this.syncWxUsers(openids, appid);
                nextOpenid = wxMpUserList.getNextOpenid();
                hasMore = StringUtils.hasText(nextOpenid) && wxMpUserList.getCount() >= 10000;
            }
        } catch (WxErrorException e) {
            log.error("同步公众号粉丝出错:", e);
        } finally {
            syncWxUserTaskRunning = false;
        }
        log.info("同步公众号粉丝列表：完成");
    }

    /**
     * 通过传入的openid列表，同步用户列表
     *
     * @param openids
     */
    @Override
    public void syncWxUsers(List<String> openids, String appid) {
        if (openids.size() < 1) {
            return;
        }
        //截取首个openid的一部分做批次号（打印日志时使用，无实际意义）
        final String batch = openids.get(0).substring(20);
        WxMpUserService wxMpUserService = wxMpService.getUserService();
        int start = 0, batchSize = openids.size(), end = Math.min(100, batchSize);
        log.info("开始处理批次：{}，批次数量：{}", batch, batchSize);

        //分批处理,每次最多拉取100个用户信息
        while (start < end && end <= batchSize) {
            final int finalStart = start, finalEnd = end;
            final List<String> subOpenids = openids.subList(finalStart, finalEnd);
            //使用线程池同步数据，否则大量粉丝数据需同步时会很慢
            TaskExcutor.submit(() -> {
                log.info("同步批次:【{}--{}-{}】，数量：{}", batch, finalStart, finalEnd, subOpenids.size());
                wxMpService.switchover(appid);
                //批量获取用户信息，每次最多100个
                List<WxMpUser> wxMpUsers = null;
                try {
                    wxMpUsers = wxMpUserService.userInfoList(subOpenids);
                } catch (WxErrorException e) {
                    log.error("同步出错，批次：【{}--{}-{}】，错误信息：{}", batch, finalStart, finalEnd, e);
                }
                if (wxMpUsers != null && !wxMpUsers.isEmpty()) {
                    List<WxUser> wxUsers = wxMpUsers.parallelStream().map(item -> new WxUser(item, appid)).collect(Collectors.toList());
                    this.saveOrUpdateBatch(wxUsers);
                }
            });
            start = end;
            end = Math.min(end + 100, openids.size());
        }
        log.info("批次：{}处理完成", batch);
    }

    /**
     * 异步批量同步用户信息
     *
     * @param openidList
     */
    @Override
    @Async
    public void refreshUserInfoAsync(String[] openidList, String appid) {
        log.info("批量更新用户信息：任务开始");
        for (String openid : openidList) {
            wxMpService.switchover(appid);
            TaskExcutor.submit(() -> this.refreshUserInfo(openid, appid));
        }
        log.info("批量更新用户信息：任务全部添加到线程池");
    }

    @Override
    public WxUser getByOpenid(String openid) {
        return getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getOpenid, openid));
    }
}